#include "assert.h"
#include "error.h"
#include "lock.h"
#include "mmu.h"
#include "dbg.h"

#include "fs/deffs.h"
#include "fs/fs.h"
#include "fs/inode.h"
#include "fs/iobuf.h"
#include "fs/vfs.h"
#include "fs/ide.h"

// #define DISK0_BLOCK_SIZE PAGE_SIZE
static int DISK0_BLOCK_SIZE;
#define DISK0_BUFFER_SIZE (4 * DISK0_BLOCK_SIZE)
#define DISK0_SECTOR_PER_BLOCK (DISK0_BLOCK_SIZE / SECTOR_SIZE)

static char* disk0_buffer;
static spinlock_t disk0_sem;

static void
lock_disk0(void) {
    spinlock_acquire(&disk0_sem);
}

static void
unlock_disk0(void) {
    spinlock_release(&disk0_sem);
}

static int
disk0_open(device_t* dev, uint32_t flags) {
    return 0;  // TODO???
}

static int
disk0_close(device_t* dev) {
    return 0;  // TODO???
}

static void
disk0_readBlocksNolock(uint32_t blockNo, uint32_t nBlocks) {
    uint32_t sectorNo = blockNo * DISK0_SECTOR_PER_BLOCK;
    uint32_t nSectors = nBlocks * DISK0_SECTOR_PER_BLOCK;
    int ret = ide_readSecs(DISK0_DEVICE_NO, sectorNo, disk0_buffer, nSectors);
    if (ret < 0)
        panic("disk0: read block no");
}

static void
disk0_writeBlocksNolock(uint32_t blockNo, uint32_t nBlocks) {
    uint32_t sectorNo = blockNo * DISK0_SECTOR_PER_BLOCK;
    uint32_t nSectors = nBlocks * DISK0_SECTOR_PER_BLOCK;
    int ret = ide_writeSecs(DISK0_DEVICE_NO, sectorNo, disk0_buffer, nSectors);
    if (ret < 0)
        panic("disk0: write block no");
}

static int
disk0_io(device_t* dev, iobuf_t* iob, bool write) {
    offset_t off = iob->io_off;
    size_t resid = iob->io_resid;
    uint32_t blockNo = off / DISK0_BLOCK_SIZE;
    uint32_t nBlocks = resid / DISK0_BLOCK_SIZE;

    if ((off % DISK0_BLOCK_SIZE != 0) || (resid % DISK0_BLOCK_SIZE != 0))
        return -E_INVAL;
    if (blockNo + nBlocks > dev->d_blocks)
        return -E_INVAL;

    if (nBlocks == 0)
        return 0;

    lock_disk0();
    while (resid != 0) {
        size_t copied, _len = __MIN(DISK0_BUFFER_SIZE, resid);
        if (write) {
            iobuf_move(iob, disk0_buffer, _len, 0, &copied);
            assert((copied != 0) && (copied <= resid) && (copied % DISK0_BLOCK_SIZE == 0));
            nBlocks = copied / DISK0_BLOCK_SIZE;
            disk0_writeBlocksNolock(blockNo, nBlocks);
        } else {
            nBlocks = _len / DISK0_BLOCK_SIZE;
            disk0_readBlocksNolock(blockNo, nBlocks);
            iobuf_move(iob, disk0_buffer, _len, 1, &copied);
            assert((copied == _len) && (copied % DISK0_BLOCK_SIZE == 0));
        }
        resid -= copied;
        blockNo += nBlocks;
    }
    unlock_disk0();
    return 0;
}

static int
disk0_ioctl(device_t* dev, int op, void* data) {
    return -E_UNIMP;
}

void
dev_init_disk0(void) {
    inode_t* node = dev_createInode();
    if (node == NULL)
        panic("disk0: dev_createInode");
    device_t* dev = vop_info(node, device);

    dev->d_blocks = ide_device_size(DISK0_DEVICE_NO) / DISK0_SECTOR_PER_BLOCK;
    dev->d_blocksize = DISK0_BLOCK_SIZE;
    dev->d_open = disk0_open;
    dev->d_close = disk0_close;
    dev->d_io = disk0_ioctl;
    semaphore_init(&(disk0_sem), 1);
    disk0_buffer = kmalloc(DISK0_BUFFER_SIZE);
    if (disk0_buffer == NULL)
        panic("disk0 alloc buffer failed");

    int ret = vfs_addDevice("disk0", node, 1);
    if (ret < 0)
        panic("disk0: vfs_addDevice");
}